using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Comms;
using Engine;
using Game;

namespace RuthlessConquest {
    public class Client : IDisposable {
        public const string fName = "RCClient";
        public Game Game { get; set; }
        public Peer Peer { get; set; }
        public bool IsUsingInProcessTransmitter { get; }
        public bool IsPaused { get; set; }
        public bool IsGameCreator { get; set; }
        public bool IsConnected => Peer.ConnectedTo != null;
        public Exception Error { get; set; }

        public Client(bool useInProcessTransmitter) {
            IsUsingInProcessTransmitter = useInProcessTransmitter;
            DiagnosticTransmitter diagnosticTransmitter = !useInProcessTransmitter
                ? new DiagnosticTransmitter(new UdpTransmitter())
                : new DiagnosticTransmitter(new InProcessTransmitter());
            Log.Information(string.Format(LanguageControl.Get(fName, "1"), diagnosticTransmitter.Address));
            Peer = new Peer(diagnosticTransmitter);
            Peer.Comm.Settings.ResendPeriods = [0.5f, 0.5f, 1f, 1.5f, 2f];
            Peer.Comm.Settings.MaxResends = 20;
            Peer.Settings.KeepAlivePeriod = IsUsingInProcessTransmitter ? float.PositiveInfinity : 5f;
            Peer.Settings.KeepAliveResendPeriod = IsUsingInProcessTransmitter ? float.PositiveInfinity : 2f;
            Peer.Settings.ConnectionLostPeriod = IsUsingInProcessTransmitter ? float.PositiveInfinity : 30f;
            Peer.Settings.ConnectTimeOut = 6f;
            Peer.Error += delegate(Exception e) {
                Error = e;
                if (e is ProtocolViolationException) {
                    Log.Warning(e);
                }
                else {
                    Log.Error(e);
                }
            };
            Peer.ConnectAccepted += delegate(PeerPacket p) {
                Message message = Message.Read(p.Bytes);
                if (!(message is GameCreatedMessage message2)) {
                    if (!(message is GameJoinedMessage message3)) {
                        throw new ProtocolViolationException(LanguageControl.Get(fName, "2"));
                    }
                    Handle(message3, p.PeerData);
                }
                else {
                    Handle(message2, p.PeerData);
                }
            };
            Peer.ConnectRefused += delegate(Packet p) {
                if (Message.Read(p.Bytes) is RefusedMessage refusedMessage) {
                    throw new ProtocolViolationException(refusedMessage.Reason);
                }
                throw new ProtocolViolationException(LanguageControl.Get(fName, "2"));
            };
            Peer.ConnectTimedOut += delegate { throw new ProtocolViolationException(LanguageControl.Get(fName, "3")); };
            Peer.DataMessageReceived += delegate(PeerPacket p) {
                Message message4 = Message.Read(p.Bytes);
                if (!(message4 is TickMessage message5)) {
                    if (!(message4 is RequestGameImageMessage message6)) {
                        if (!(message4 is RequestGameStateMessage message7)) {
                            if (!(message4 is Z_PlayerLocationsMessage message8)) {
                                throw new ProtocolViolationException(LanguageControl.Get(fName, "2"));
                            }
                            Handle(message8, p.PeerData);
                        }
                        else {
                            Handle(message7, p.PeerData);
                        }
                    }
                    else {
                        Handle(message6, p.PeerData);
                    }
                }
                else {
                    Handle(message5, p.PeerData);
                }
            };
            Peer.Start();
            Window.Activated += WindowActivated;
            Window.Deactivated += WindowDeactivated;
            Window.Closed += WindowClosed;
        }

        public void Dispose() {
            Window.Activated -= WindowActivated;
            Window.Deactivated -= WindowDeactivated;
            Window.Closed -= WindowClosed;
            DisconnectFromGame();
            Task.Run(
                delegate {
                    Thread.Sleep(2000);
                    Peer?.Dispose();
                }
            );
        }

        public void CreateGame(IPEndPoint serverAddress, GameCreationParameters gameCreationParameters) {
            lock (Peer.Lock) {
                if (Peer.ConnectedTo != null
                    || Peer.ConnectingTo != null) {
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "4"));
                }
                Error = null;
                IsGameCreator = true;
                Peer.Connect(serverAddress, Message.Write(new CreateGameMessage { CreationParameters = gameCreationParameters }));
            }
        }

        public void JoinGame(IPEndPoint serverAddress, int gameId, bool spectate) {
            lock (Peer.Lock) {
                if (Peer.ConnectedTo != null
                    || Peer.ConnectingTo != null) {
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "4"));
                }
                Error = null;
                IsGameCreator = false;
                Peer.Connect(
                    serverAddress,
                    Message.Write(
                        new JoinGameMessage {
                            GameId = gameId,
                            PlayerName = SettingsManager.PlayerName,
                            PlayerGuid = SettingsManager.PlayerGuid,
                            PlayerPlatform = VersionsManager.Platform,
                            PreferredFaction = spectate ? Faction.None : SettingsManager.Faction
                        }
                    )
                );
            }
        }

        public void StartGame() {
            lock (Peer.Lock) {
                if (Peer.ConnectedTo == null) {
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "5"));
                }
                if (!IsGameCreator) {
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "6"));
                }
                Peer.SendDataMessage(Peer.ConnectedTo, DeliveryMode.ReliableSequenced, Message.Write(new StartGameMessage()));
            }
        }

        public void DisconnectFromGame() {
            Peer.Disconnect();
        }

        public void Update() {
            if (Game == null) {
                return;
            }
            if (!IsPaused) {
                Game.Update();
            }
            if (Game.StepModule.OutgoingOrders.Count > 0) {
                lock (Peer.Lock) {
                    if (Peer.ConnectedTo != null) {
                        PlayerOrdersMessage playerOrdersMessage = new();
                        playerOrdersMessage.Orders.AddRange(Game.StepModule.OutgoingOrders);
                        Peer.SendDataMessage(Peer.ConnectedTo, DeliveryMode.Reliable, Message.Write(playerOrdersMessage));
                    }
                }
                Game.StepModule.OutgoingOrders.Clear();
            }
            if (Game.StepModule.OutgoingStateHashMessages.Count <= 0) {
                return;
            }
            lock (Peer.Lock) {
                if (Peer.ConnectedTo != null) {
                    foreach (GameStateHashMessage outgoingStateHashMessage in Game.StepModule.OutgoingStateHashMessages) {
                        Peer.SendDataMessage(Peer.ConnectedTo, DeliveryMode.Reliable, Message.Write(outgoingStateHashMessage));
                    }
                }
            }
            Game.StepModule.OutgoingStateHashMessages.Clear();
        }

        public void Draw(Color colorTransform) {
            Game?.Draw(colorTransform);
        }

        public void Handle(GameCreatedMessage message, PeerData peerData) {
            Dispatcher.Dispatch(
                delegate {
                    if (Game == null) {
                        Game = new Game(message.CreationParameters);
                        return;
                    }
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "7"));
                }
            );
        }

        public void Handle(GameJoinedMessage message, PeerData peerData) {
            Dispatcher.Dispatch(
                delegate {
                    if (Game == null) {
                        Game = GameState.ToGame(message.GameState);
                        Game.StepModule.IncomingTickMessages.AddRange(message.TickMessages);
                        return;
                    }
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "7"));
                }
            );
        }

        public void Handle(TickMessage message, PeerData peerData) {
            Dispatcher.Dispatch(
                delegate {
                    if (Game != null) {
                        Game.StepModule.IncomingTickMessages.Add(message);
                        return;
                    }
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "8"));
                }
            );
        }

        public void Handle(RequestGameImageMessage message, PeerData peerData) {
            Dispatcher.Dispatch(
                delegate {
                    if (Game != null) {
                        GameImageMessage message2 = new() { GameImage = GameImage.FromGame(Game) };
                        Peer.SendDataMessage(Peer.ConnectedTo, DeliveryMode.Reliable, Message.Write(message2));
                        return;
                    }
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "8"));
                }
            );
        }

        public void Handle(RequestGameStateMessage message, PeerData peerData) {
            Dispatcher.Dispatch(
                delegate {
                    if (Game != null) {
                        GameStateMessage message2 = new() {
                            GameState = GameState.FromGame(Game, false),
                            NonDefeatedFactions = (from p in Game.PlayersModule.Players
                                where p.Status == FactionStatus.Undecided
                                select p.Faction
                                into f
                                orderby Game.PlanetsModule.GetFactionProductionPercentage(f)
                                select f).ToDynamicArray()
                        };
                        Peer.SendDataMessage(Peer.ConnectedTo, DeliveryMode.Reliable, Message.Write(message2));
                        return;
                    }
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "8"));
                }
            );
        }

        public void Handle(Z_PlayerLocationsMessage message, PeerData peerData) {
            Dispatcher.Dispatch(
                delegate {
                    if (Game != null) {
                        Game.PlayersModule.PlayerLocations = message.PlayerLocations;
                        Game.PlayersModule.PlayerAddresses = message.PlayerAddresses;
                        return;
                    }
                    throw new ProtocolViolationException(LanguageControl.Get(fName, "8"));
                }
            );
        }

        public void WindowActivated() {
            if (IsUsingInProcessTransmitter) {
                IsPaused = false;
            }
        }

        public void WindowDeactivated() {
            if (IsUsingInProcessTransmitter) {
                IsPaused = true;
            }
        }

        public void WindowClosed() {
            Dispose();
            if (!IsUsingInProcessTransmitter) {
                Thread.Sleep(750);
            }
        }
    }
}